/* 
 * Charles Douglas
 * CSCE 4230
 */
#include <math.h>
#include <GL/glut.h>
#include <stdlib.h>

/* number of sections per side, equivalent to sqrt(number of squares) */
int k=100;

/* number of vertices = (k+1)^2 */
int num_vertices = 10201;

/* number of triangles  = 2*k^2 */
int num_triangles = 20000;


GLdouble vertices[30603];
GLdouble normals[30603];
int triangles[60000];

static int xrot = 0, yrot = 0;
static double zoom=1.0;

GLdouble norm(GLdouble x, GLdouble y, GLdouble z)
{
    return sqrt(x*x + y*y + z*z);
}

void calc_normal_for_triangle(int v1, int v2, int v3)
{
   GLdouble v1_x = vertices[v1];
   GLdouble v1_y = vertices[v1+1];
   GLdouble v1_z = vertices[v1+2];

   GLdouble v2_x = vertices[v2];
   GLdouble v2_y = vertices[v2+1];
   GLdouble v2_z = vertices[v2+2];

   GLdouble v3_x = vertices[v3];
   GLdouble v3_y = vertices[v3+1];
   GLdouble v3_z = vertices[v3+2];

   /* 1, 2, and 3 are in ccw order, so (v2-v1)x(v3-v1) is a normal*/

   GLdouble c1_x = v2_x - v1_x;
   GLdouble c1_y = v2_y - v1_y;
   GLdouble c1_z = v2_z - v1_z;

   GLdouble c2_x = v3_x - v1_x;
   GLdouble c2_y = v3_y - v1_y;
   GLdouble c2_z = v3_z - v1_z;

   /* get the cross product */
   GLdouble n_x = c1_y*c2_z - c1_z*c2_y;
   GLdouble n_y = -1.0*(c1_x*c2_z - c1_z*c2_x);
   GLdouble n_z = c1_x*c2_y - c1_y*c2_x;

   GLdouble n = norm(n_x, n_y, n_z);

   normals[v1] +=  n_x/n;
   normals[v1+1] += n_y/n;
   normals[v1+2] += n_z/n; 

   normals[v2] += n_x / n;
   normals[v2+1] += n_y/n;
   normals[v2+2] += n_z/n; 

   normals[v2] += n_x / n;
   normals[v2+1] += n_y/n;
   normals[v2+2] += n_z/n; 
}

void setup_normals()
{
   int i;
   for(i=0; i < num_triangles*3; i=i+3)
   {
       calc_normal_for_triangle(triangles[i], triangles[i+1], triangles[i+2]);
   }
}


/* helper function to dump arrays */
void dump_verts()
{
   int i;
   for (i=0; i < 3*num_vertices; i=i+3)
   {
      printf("%d [x=%f, y=%f, z=%f] \n", i, vertices[i], vertices[i+1], vertices[i+2]);
   }
}

void dump_triangles()
{
   int i;
   int v1;
   int v2;
   int v3;
   for (i=0; i < 3*num_triangles; i=i+3)
   {
      v1=triangles[i];
      v2=triangles[i+1];
      v3=triangles[i+2];
      printf("%d [%f, %f, %f]\n %d [%f, %f, %f]\n %d [%f, %f, %f]\n\n", 
            v1, vertices[v1], vertices[v1 + 1], vertices[v1 + 2],
            v2, vertices[v2], vertices[v2 + 1], vertices[v2 + 2],
            v3, vertices[v3], vertices[v3 + 1], vertices[v3 + 2]);
   }

}

dump_normals()
{
   int i;
   for (i=0; i < 3*num_vertices; i=i+3)
   {
      printf("%d [x=%f, y=%f, z=%f] \n", i, normals[i], normals[i+1], normals[i+2]);
   }
}

/* the function of x and y used to find z */
double f(double x, double y)
{
   return .5*exp(-.04*sqrt((80*x-40)*(80*x-40) + (90*y-45)*(90*y-45))) *
                       cos(0.15*sqrt((80*x-40)*(80*x-40) + (90*y-45)*(90*y-45)));
}

/* sets up the values for the rectangle located at k_x, k_y in the 
global array, k_x and k_y are values from 1 to k */



void setup_quad(int k_x, int k_y, int tri_offset)
{

   int offset = 3*((k_y-1)*(k+1) + (k_x-1));
   /*find the corners*/
   int llc = offset;
   int lrc = offset + 3;
   int ulc = offset + 3*(k+1);
   int urc = offset + 3*(k+2);
   

   /*lower left corner - x, y, z */
   vertices[llc] = (1.0 / (double) k) * ((double) k_x - 1.0);
   vertices[llc + 1] = (1.0 / (double) k) * ((double) k_y - 1.0);
   vertices[llc + 2] = f(vertices[llc], vertices[llc + 1]);
/*   printf("lower left corner at %d: [%f, %f, %f]\n", 
       llc, vertices[llc], vertices[llc+1], vertices[llc+2]); */

   /*lower right corner - x, y, z */
   vertices[lrc] = (1.0 / (double) k) * ((double) k_x);
   vertices[lrc + 1] = (1.0 / (double) k) * ((double) k_y - 1.0);
   vertices[lrc + 2] = f(vertices[lrc], vertices[lrc + 1]);
/*   printf("lower right corner at %d: [%f, %f, %f]\n", 
       lrc, vertices[lrc], vertices[lrc+1], vertices[lrc+2]); */
   /*upper left corner - x, y, z */

   vertices[ulc] = (1.0 / (double) k) * ((double) k_x - 1.0);
   vertices[ulc + 1] = (1.0 / (double) k) * ((double) k_y);
   vertices[ulc + 2] = f(vertices[ulc], vertices[ulc + 1]);
/*   printf("upper left corner at %d: [%f, %f, %f]\n", 
       ulc, vertices[ulc], vertices[ulc+1], vertices[ulc+2]); */

   /*upper right corner - x, y, z */
   vertices[urc] = (1.0 / (double) k) * ((double) k_x);
   vertices[urc + 1] = (1.0 / (double) k) * ((double) k_y);
   vertices[urc + 2] = f(vertices[urc], vertices[urc + 1]);
/*   printf("upper right corner at %d: [%f, %f, %f]\n\n", 
       urc, vertices[urc], vertices[urc+1], vertices[urc+2]); */

   /*first triangle -- llc, lrc, urc */
   triangles[tri_offset] = llc;
   triangles[tri_offset + 1] = lrc;
   triangles[tri_offset + 2] = urc;

   /*second triangle -- llc, urc, ulc */
   triangles[tri_offset + 3] = urc;
   triangles[tri_offset + 4] = ulc;
   triangles[tri_offset + 5] = llc;
}

void setup(void)
{
   int i;
   int j;
   int tri_offset = 0;

   /*outer loop for y */
   for (i=1; i <= k; i++)
   {
      /*inner loop for x */
      for (j=1; j <= k; j++)
      {
         setup_quad(j, i, tri_offset);
         tri_offset+=6;
      }
   }

   setup_normals();
}

void init(void) 
{
   GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat mat_shininess[] = { 50.0 };
   GLfloat light_position[] = { 2.0, 1.0, 1.0, 0.0 }; 
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glShadeModel (GL_SMOOTH);

   glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
   glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
   glLightfv(GL_LIGHT0, GL_POSITION, light_position);


   glEnable(GL_NORMALIZE);
   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST); 

}

void display(void)
{
   int i;
   int v;

   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   glFrustum(-1.0*zoom, 1.0*zoom, -1.0*zoom, 1.0*zoom, 2, 20);
   glMatrixMode(GL_MODELVIEW);

   glLoadIdentity();

   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glPushMatrix();
      glTranslatef(0, 0, -3.0);
      glRotatef ((GLfloat) xrot, 1.0, 0.0, 0.0);
      glRotatef ((GLfloat) yrot, 0.0, 1.0, 0.0);

      glColor3d(1.0, 1.0, 1.0);

       /* could not get glDrawElements to work... */
/*       glEnableClientState(GL_NORMAL_ARRAY);
       glEnableClientState(GL_VERTEX_ARRAY);
       glEnableClientState(GL_INDE      glTranslatef(0, 0, 3.0);X_ARRAY);

       glIndexPointer(GL_UNSIGNED_INT, 0, triangles);
       glVertexPointer(3, GL_DOUBLE, 0, vertices);
       glNormalPointer(GL_DOUBLE, 0, normals);
       glDrawElements(GL_TRIANGLES, 3*num_triangles, GL_UNSIGNED_INT, triangles); 

       glDisableClientState(GL_NORMAL_ARRAY);
       glDisableClientState(GL_VERTEX_ARRAY);
       glDisableClientState(GL_INDEX_ARRAY);
*/   
      glBegin(GL_TRIANGLES);
         for(i=0; i < 3*num_triangles; i=i+3)
         {
            v=triangles[i];
/*            printf("DRAWING VERTEX AT [%f, %f, %f] \n", 
                vertices[v],
                vertices[v+1],
                vertices[v+2]); */
            glVertex3f(vertices[v] - 0.5, vertices[v+1] - 0.5, vertices[v+2]);
            glNormal3f(normals[v], normals[v+1], normals[v+2]);

            v=triangles[i+1];
/*            printf("DRAWING VERTEX AT [%f, %f, %f] \n", 
                vertices[v],
                vertices[v+1],
                vertices[v+2]); */
            glVertex3f(vertices[v] - 0.5, vertices[v+1] - 0.5, vertices[v+2]);
            glNormal3f(normals[v], normals[v+1], normals[v+2]);

            v=triangles[i+2];
/*            printf("DRAWING VERTEX AT [%f, %f, %f] \n\n", 
                vertices[v],
                vertices[v+1],
                vertices[v+2]); */
            glVertex3f(vertices[v] - 0.5, vertices[v+1]- 0.5, vertices[v+2]);
            glNormal3f(normals[v], normals[v+1], normals[v+2]);
         }
      glEnd(); 

   glPopMatrix();


   glutSwapBuffers();
}

void reshape (int w, int h)
{
   glViewport (0, 0, (GLsizei) w, (GLsizei) h); 


}

void keyboard (unsigned char key, int x, int y)
{
   switch (key) {
      case 'x':
         xrot = (xrot + 5) % 360;
         glutPostRedisplay();
         break;
      case 'X':
         xrot = (xrot - 5) % 360;
         glutPostRedisplay();
         break;
      case 'y':
         yrot = (yrot + 5) % 360;
         glutPostRedisplay();
         break;
      case 'Y':
         yrot = (yrot - 5) % 360;
         glutPostRedisplay();
         break;
      case 'z':
         zoom = 0.8*zoom;
         glutPostRedisplay();
         break;
      case 'Z':
         zoom=1.2*zoom;
         glutPostRedisplay();
         break;
      case 27:
         exit(0);
         break;
      default:
         break;
   }
}

void menuResponse(int value)
{
    keyboard(value, 0, 0);
}


int main(int argc, char** argv)
{
   int menu;
   setup();

   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   glutInitWindowSize (500, 500); 
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);

   menu=glutCreateMenu(menuResponse);
   glutAddMenuEntry("Zoom in (z)", 'z');
   glutAddMenuEntry("Zoom out (Z)", 'Z');
   glutAddMenuEntry("Rotate about the x-axis forward (x)", 'x');
   glutAddMenuEntry("Rotate about the x-axis backward (X)", 'X');
   glutAddMenuEntry("Rotate about the y-axis forward (y)", 'y');
   glutAddMenuEntry("Rotate about the y-axis backward (Y)", 'Y');
   glutAddMenuEntry("Quit (esc)", 27);
   glutAttachMenu(GLUT_RIGHT_BUTTON);

   init ();
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}
